iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Software Development

文藝復興:這段程式碼,好像有點味道系列 第 14

Day 14|達文西退後三步:結構對了,還要整幅畫都對得上

  • 分享至 

  • xImage
  •  

達文西畫完一張臉的透視,不會就此收筆
他會退後三步,看這張臉跟整幅畫其他物件的消失點,是不是還落在同一個點上

單一物件的結構畫對了,不代表整幅畫的空間邏輯是一致的
兩個物件各自的透視都沒問題,擺在一起卻可能互相矛盾

模組二的五個警訊也是一樣,「各自修對了,不代表放在同一個系統裡,還撐得住彼此」

兩個警訊,其實是同一個結構問題的兩面

Day 11 拆過一次 PaymentMethod,把「一定能做到」的 Charge,留在父類別
把「不是每種付款方式都做得到」的 Refund,搬進 RefundablePaymentMethod 這個子類別

這解決了里氏替換的問題(子類別不會再對著承諾的方法丟例外)

但退後三步看整個系統,會發現另一個地方,還藏著一組跟這個結構緊密相關的判斷:

// 🔴 選擇該用哪一種付款方式的邏輯,散落在下單流程裡
public PaymentMethod ResolvePaymentMethod(string paymentType)
{
    switch (paymentType)
    {
        case "CreditCard": return new CreditCardPayment();
        case "CashOnDelivery": return new CashOnDeliveryPayment();
        default: throw new ArgumentException("未知的付款方式");
    }
}

這個 switch,看起來很眼熟,跟 Day 09 的通知管道是同一種形狀

這個 switch,要不要拆成多型?

先別急著動手拆,這個 switch 有幾個跟 Day 09 通知範例不一樣的地方:

  • 它只出現在一個地方,在一個工廠方法內,唯一的職責就是「決定該 new 哪一個具體付款方式」
  • 系統裡沒有第二個地方,重複判斷「這是哪一種付款方式」

回頭看 Day 09 的結論:switch 用在工廠裡,本身就是多型結構的入口,不是問題

真正該處理的,不是這個 switch,是它產出的物件 CreditCardPaymentCashOnDeliveryPayment

彼此之間該不該共用同一個父類別的所有方法,這正是 Day 11 已經處理過的里氏替換問題

結構對不對,要看兩層:

  • 型別內部的繼承關係對不對(Day 11)
  • 選擇型別的地方,有沒有不必要地重複判斷(Day 09)

兩層各自檢查,結論才完整

退後三步,常抓到的三種矛盾

  • 同一組型別判斷,出現在不只一個地方
    • 工廠裡判斷過一次,某個報表邏輯又判斷了一次,兩邊沒有同步更新的機制
  • 子類別覆寫父類別方法,卻改變了呼叫端的預期行為
    • 不只是拋例外,也包括默默回傳跟父類別不同語意的值
  • 兩個介面命名不同、卻做著同一件事
    • 這是 Day 12「異曲同工的類別」要處理的警訊,退遠一點看,常常是在同一次檢查裡順便發現的

不是每一種變化,都值得換成多型

多型的成本,是多幾個類別、多一層間接
如果某個型別判斷,已知只有兩三種、而且幾乎不會再增加,一段清楚的 if/else 有時反而比硬拆出五個只有一行程式碼的類別,更容易讀

判斷的關鍵不是「有沒有型別判斷」,是:
「這個判斷,未來會不會反覆長出新的分支、會不會在超過一個地方重複出現」

只有符合這兩個條件之一,才值得換成多型

自我檢查清單

  1. 這個型別判斷,是只在一個地方出現(例如工廠),還是同一組邏輯散落在好幾個角落?
  2. 這組型別背後的繼承結構,換一顆子類別進去,行為會不會偷偷變了?
  3. 有沒有兩個類別做著同一件事,介面命名卻各自為政?
  4. 這裡如果硬拆成多型,換來的彈性,值得多出來的幾個類別嗎?
  5. 退遠一點看整個模組,今天處理的結構,跟系統裡其他地方的同類結構,是不是用同一套邏輯在運作?

明日預告

明天我們進入模組三 「濕壁畫的灰泥」
一旦抹上牆,就有了乾燥的時限

變更的妨礙者,正式開工


上一篇
Day 13|租來的畫室,牆上釘不得釘子:不完整的程式庫類別 (Incomplete Library Class)
下一篇
Day 15|改一個需求,卻要在同一幅畫裡到處補筆:發散式變更 (Divergent Change)
系列文
文藝復興:這段程式碼,好像有點味道27
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言